package com.cnsugar.ai.face;

import com.cnsugar.ai.face.bean.FaceIndex;
import com.cnsugar.ai.face.dao.FaceDao;
import com.cnsugar.common.sqlite.JdbcPool;
import com.seetaface2.SeetaFace2JNI;
import com.seetaface2.model.SeetaImageData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.DefaultResourceLoader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;


/**
 * @Author Sugar
 * @Version 2019/4/22 14:28
 */
public class SeetafaceBuilder {
    public static volatile   Logger logger = LoggerFactory.getLogger(SeetafaceBuilder.class);
    public volatile static SeetaFace2JNI seeta = null;

    public enum FacedbStatus {
        READY, LOADING, OK, INACTIV;
    }
    // 将人脸数据库初始化为等待状态
    public volatile static FacedbStatus face_db_status = FacedbStatus.READY;

    public static SeetaFace2JNI build() {
        if (seeta == null) {
            synchronized (SeetafaceBuilder.class) {
                if (seeta != null) {
                    return seeta;
                }
                init();
            }
        }
        return seeta;
    }

    /**
     * 建立人脸库索引
     */
    public static void buildIndex() {
        synchronized (SeetafaceBuilder.class) {
            while (face_db_status == FacedbStatus.LOADING || face_db_status == FacedbStatus.READY) {
                //等待之前的任务初始化完成
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
            face_db_status = FacedbStatus.READY;
            new Thread(() -> {
                seeta.clear();
                loadFaceDb();
            }).start();
        }
    }

    /**
     * 返回人脸数据库状态
     * @return
     */
    public static FacedbStatus getFaceDbStatus() {
        return face_db_status;
    }

    private static void init() {
        Properties prop = getConfig();
        String separator = System.getProperty("path.separator");
        String sysLib = System.getProperty("java.library.path");
        if (sysLib.endsWith(separator)) {
            System.setProperty("java.library.path", sysLib + prop.getProperty("libs.path", ""));
        } else {
            System.setProperty("java.library.path", sysLib + separator + prop.getProperty("libs.path", ""));
        }
        try {//使java.library.path生效
            Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
            sysPathsField.setAccessible(true);
            sysPathsField.set(null, null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

        String[] libs = prop.getProperty("libs", "").split(",");
//        for (String lib : libs) {
//            logger.debug("load library: {}", lib);
//            System.loadLibrary(lib);
//        }
        // 多线程加载资源
        LoadLibrary loadLibrary = new LoadLibrary(libs);
        // 开启两个线程加载资源库
        new Thread(loadLibrary,"load_1").start();
        new Thread(loadLibrary,"load_2").start();
        // 判断是否需要等待加载完成
        while (LoadLibrary.getLoadStatus()==LoadLibrary.LoadStatus.READY||LoadLibrary.getLoadStatus()==LoadLibrary.LoadStatus.LOADING){
            try {
                Thread.sleep(1*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        String bindata = prop.getProperty("bindata.dir");
        logger.debug("bindata dir: {}", bindata);
        seeta = new SeetaFace2JNI();
        // 初始化人脸模型
        seeta.initModel(bindata);
        String db_file = prop.getProperty("sqlite.db.file");
        if (db_file != null) {
            // seetaface数据库设置到系统属性中
            System.setProperty("seetaface.db", db_file);
            // 设置数据库连接池属性
            System.setProperty(JdbcPool.MAX_TOTAL, prop.getProperty(JdbcPool.MAX_TOTAL));
            System.setProperty(JdbcPool.MAX_IDLE, prop.getProperty(JdbcPool.MAX_IDLE));
            System.setProperty(JdbcPool.MIN_IDLE, prop.getProperty(JdbcPool.MIN_IDLE));
            System.setProperty(JdbcPool.MAX_WAIT_MILLIS, prop.getProperty(JdbcPool.MAX_WAIT_MILLIS));
            // 新开线程，加载人脸进入内存
            //new Thread(() -> loadFaceDb()).start();
            loadFaceDb();
        } else {
            face_db_status = FacedbStatus.INACTIV;
            logger.warn("没有配置sqlite.db.file，人脸注册(register)及人脸搜索(1 v N)功能将无法使用!!!");
        }
        logger.info("Seetaface init completed!!!");
    }

    /**
     * 加载人脸库
     */
    private static void loadFaceDb() {
        if (face_db_status != FacedbStatus.READY) {
            return;
        }

        if (System.getProperty("seetaface.db") == null) {
            face_db_status = FacedbStatus.INACTIV;
            logger.error("没有配置sqlite.db.file!!!");
            return;
        }

        face_db_status = FacedbStatus.LOADING;
        logger.info("load face data...");
        FaceDao.clearIndex();
        int pageNo = 0, pageSize = 100;
        while (true) {
            List<FaceIndex> list = FaceDao.findFaceImgs(pageNo, pageSize); // face_image表中获取
            if (list == null) {
                break;
            }
            int size = list.size();
            // 将集合均分成两部分，分为两个线程去加载
            int middle=size%2==0?size/2:size/2+1;
            List<FaceIndex> list1 = list.subList(0, middle);
            List<FaceIndex> list2 = list.subList(middle, size);
            LoadFaceThread load_face_1 = new LoadFaceThread(list1);
            LoadFaceThread load_face_2 = new LoadFaceThread(list2);
            // 分别开启两个线程去加载人脸库浸入内存
            new Thread(load_face_1,"load_face_1").start();
            new Thread(load_face_2,"load_face_2").start();
            // 当线程加载完毕则会跳出循环，否则主线程睡眠等待线程执行完成。
            while (load_face_1.getStatus()==false||load_face_2.getStatus()==false){
                try {
                    Thread.sleep(5*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
//            list.forEach(face -> {
//                try {
//                    // seetaface2开源的只能注册500张人脸
//                    register(face.getKey(), face);
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//            });
            // 当分页查询小于100后，说明加载完毕，跳出循环
            if (list.size() < pageSize) {
                break;
            }
            // 分页数加一
            pageNo++;
        }
        face_db_status = FacedbStatus.OK;
    }

    /**
     * 将历史注册过的所有人脸重新加载到内存库中
     *
     * @param key  人脸照片唯一标识
     * @param face 人脸照片
     * @return
     * @throws IOException
     */
    public  static void register(String key, FaceIndex face) {
        SeetaImageData imageData = new SeetaImageData(face.getWidth(), face.getHeight(), face.getChannel());
        imageData.data = face.getImgData();
        // native方法
        int index = seeta.register(imageData);
        if (index < 0) {
            logger.info("Register face fail: key={}, index={}", key, index);
            return;
        }
        FaceIndex faceIndex = new FaceIndex();
        faceIndex.setKey(key);
        faceIndex.setIndex(index);
        // 存储到本地sqlite数据库
        FaceDao.saveOrUpdateIndex(faceIndex); //
            logger.info(Thread.currentThread().getName() + "  Register face success: key={}, index={}", key, index);
    }
    // 配置文件的配置内容
    public static Properties getConfig() {
        Properties properties = new Properties();
        // 配置文件地址
        String location = "classpath:/seetaface.properties";
        try (InputStream is = new DefaultResourceLoader().getResource(location).getInputStream()) {
            properties.load(is);
            logger.debug("seetaface config: {}", properties.toString());
        } catch (IOException ex) {
            logger.error("Could not load property file:" + location, ex);
        }
        return properties;
    }
}

